import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import org.apache.xmlbeans.XmlCursor; // 注意这个重要的类

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.List;

public class Excel1 {

    public static void main(String[] args) {
        String inputPath = "./test.docx";
        String outputPath = "output.docx";
        String placeholder = "${dynamic_table}";

        // 使用 try-with-resources 语句，可以确保文件流在使用后自动关闭，非常安全。
        try (FileInputStream fis = new FileInputStream(inputPath);
             XWPFDocument document = new XWPFDocument(fis)) {

            // --- 核心步骤 1: 找到占位符所在的段落 ---
            // 我们调用一个自己写的辅助方法来完成这个任务。
            XWPFParagraph placeholderParagraph = findPlaceholderParagraph(document, placeholder);

            // --- 核心步骤 2: 检查是否找到了 ---
            // 必须进行判断，如果模板里没有这个占位符，程序就没必要继续执行了。
            if (placeholderParagraph != null) {

                // --- 核心步骤 3: 获取插入位置的光标（这是最关键的一步！）---
                // 把 placeholderParagraph 理解成屏幕上 "${dynamic_table}" 这一行。
                // 我们需要在“这一行之前”插入我们的表格。
                // XmlCursor 就是一个“光标”，它能精确地定位到文档XML结构中的某个点。
                // placeholderParagraph.getCTP() 是获取这个段落底层的XML对象。
                // .newCursor() 就是在这个XML对象的开头创建一个光标。
                XmlCursor cursor = placeholderParagraph.getCTP().newCursor();

                // --- 核心步骤 4: 在光标位置插入一个新表格 ---
                // document.insertNewTbl(cursor) 的意思是：
                // “嘿，文档对象，请在'光标'所在的位置，给我插入一个全新的、空的表格。”
                // 这个方法会返回新建的表格对象，方便我们后续操作。
                XWPFTable table = document.insertNewTbl(cursor);

                // --- 核心步骤 5: 填充这个新创建的表格 ---
                // 同样，我们调用一个辅助方法来让代码更整洁。
                // 把空的 table 对象传进去，让那个方法负责填入数据和样式。
                populateTable(table);

                // --- 核心步骤 6: 删除原来的占位符段落 ---
                // 表格已经插好了，但 `${dynamic_table}` 这一行还在，我们需要删掉它。
                // 首先，要找到这个段落在整个文档中的位置（索引）。
                int pPos = document.getPosOfParagraph(placeholderParagraph);
                // 然后，调用 removeBodyElement 按索引删除它。
                document.removeBodyElement(pPos);

                // --- 核心步骤 7: 保存修改后的文档 ---
                // 所有在内存中的修改（插入表格、删除段落）都需要写回到一个新文件中。
                try (FileOutputStream fos = new FileOutputStream(outputPath)) {
                    document.write(fos);
                    System.out.println("文档生成成功，表格已替换占位符！");
                }
            } else {
                System.out.println("未在文档中找到占位符: " + placeholder);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 辅助方法1: 查找包含指定占位符的段落
     */
    private static XWPFParagraph findPlaceholderParagraph(XWPFDocument document, String placeholder) {
        // 遍历文档中的所有段落
        for (XWPFParagraph p : document.getParagraphs()) {
            // p.getText() 会获取这个段落的全部纯文本内容。
            // 我们检查这个文本是否包含我们的目标占位符。
            if (p.getText().contains(placeholder)) {
                // 如果找到了，就立刻返回这个段落对象。
                return p;
            }
        }
        // 如果遍历完所有段落都没找到，就返回 null。
        return null;
    }

    /**
     * 辅助方法2: 填充表格数据和样式
     */
    private static void populateTable(XWPFTable table) {
        // 设置表格宽度占满页面宽度，看起来更美观。
        table.setWidth("100%");

        // --- 创建表头 ---
        // table.getRow(0) 获取表格被创建时自带的第一行。
        XWPFTableRow headerRow = table.getRow(0);

        // 我们用另一个更小的辅助方法来创建和美化单元格，避免重复代码。
        // 创建第1个单元格（表头）
        createStyledCell(headerRow, 0, "ID", "BFBFBF", 800);
        // 创建第2个单元格（表头）
        createStyledCell(headerRow, 1, "姓名", "BFBFBF", 2000);
        // 创建第3个单元格（表头）
        createStyledCell(headerRow, 2, "部门", "BFBFBF", 4000);

        // --- 准备动态数据 ---
        // 在真实项目中，这些数据通常来自数据库查询或API调用。
        List<String[]> data = List.of(
                new String[]{"1", "张三", "技术部"},
                new String[]{"2", "李四", "市场部"},
                new String[]{"3", "王五", "人事部"}
        );

        // --- 循环添加数据行 ---
        for (String[] rowData : data) {
            // 为每一条数据创建一个新的行
            XWPFTableRow dataRow = table.createRow();
            // 在新行中填充单元格数据
            dataRow.getCell(0).setText(rowData[0]);
            dataRow.getCell(1).setText(rowData[1]);
            dataRow.getCell(2).setText(rowData[2]);
        }
    }

    /**
     * 辅助方法3: 创建一个带样式的单元格
     */
    private static void createStyledCell(XWPFTableRow row, int cellIndex, String text, String bgColor, int width) {
        // 获取指定位置的单元格，如果不存在就创建它。
        XWPFTableCell cell = row.getCell(cellIndex);
        if (cell == null) {
            cell = row.createCell();
        }

        // --- 设置单元格里的文字和样式 ---
        // 一个单元格里也至少有一个段落，我们获取第一个段落。
        XWPFParagraph p = cell.getParagraphs().get(0);
        // 在段落里创建一个 Run 来放文字。
        XWPFRun run = p.createRun();
        run.setText(text);
        run.setBold(true); // 加粗
        p.setAlignment(ParagraphAlignment.CENTER); // 段落内容水平居中

        // --- 设置单元格本身的样式 ---
        // CTTcPr 是单元格属性的底层XML对象，通过它能进行高级设置。
        CTTcPr tcPr = cell.getCTTc().addNewTcPr();
        // 设置宽度
        tcPr.addNewTcW().setW(BigInteger.valueOf(width));
        // 设置背景色
        tcPr.addNewShd().setFill(bgColor);
        // 设置垂直居中
        tcPr.addNewVAlign().setVal(STVerticalJc.CENTER);
    }
}